﻿module SSfunc
open System
open Spreadsheet

/// extract a float value from a generic object or return 'nan'
let extractFloat (elem: obj) =
    match elem with
    | null -> 0. // empty argument
    | :? float as n -> n
    | :? IRange as r ->
         match Seq.head (r.ValueSeq()) with
         | :? float as n -> n
         | _ -> nan
    | :? bool as b -> if b then 1. else 0.
    | :? string as s -> 
        if String.IsNullOrWhiteSpace(s) then 0.
        else
            let v = ref 0.
            if Double.TryParse(s, v) then v.contents else nan
    | _ -> nan
    


let Indirect (param:obj list) =
    try
       let rif = param.[0].ToString()
       wbk.[rif]
    with _ -> box nan


(* Trigonometric functions *)

let Sin (param:obj list) = sin (extractFloat param.[0])
let Cos (param:obj list) = cos (extractFloat param.[0])
let Tan (param:obj list) = tan (extractFloat param.[0])

let Sinh (param:obj list) = sinh (extractFloat param.[0])
let Cosh (param:obj list) = cosh (extractFloat param.[0])
let Tanh (param:obj list) = tanh (extractFloat param.[0])

let Asin (param:obj list) = asin (extractFloat param.[0])
let Acos (param:obj list) = acos (extractFloat param.[0])
let Atan (param:obj list) = atan (extractFloat param.[0])


(* Mathematical functions *)

let Rand (param:obj list) = (new Random()).NextDouble()
let RandBetween (param:obj list) =
    let min = (extractFloat(param.[0]))
    let max = (extractFloat(param.[1]))
    if min=nan || max=nan then nan
    else 
        float ((new Random()).Next(int min,int max))

let Sign (param:obj list) = Math.Sign(extractFloat param.[0])
let Abs (param:obj list) = Math.Abs(extractFloat param.[0])

let Log (param:obj list) =
    let baselog =
        if param.Tail.IsEmpty then 
            10.
        else
            extractFloat param.[1]
    Math.Log(extractFloat param.Head, baselog)
   
let Ln (param:obj list) = log (extractFloat param.[0])
    
let Power (param:obj list) =
    let bs = extractFloat(param.[0])
    let esp = extractFloat(param.[1])
    bs**esp

let Exp (param:obj list) = Math.Exp(extractFloat(param.[0]))

let Sqrt (param:obj list) = sqrt (extractFloat param.[0])
let SqrtPi (param:obj list) = sqrt (Math.PI * extractFloat(param.[0]))


let Fibonacci (param:obj list) =
    let n = (extractFloat(param.[0]))
    if n=nan || n<0. then nan 
    else
        let a = ((1.+sqrt(5.))/2.)**n
        let b = ((1.-sqrt(5.))/2.)**n
        round ((1./sqrt 5.)*(a-b))


let rec private fact step = function 
    | 0L | 1L -> 1L
    | n -> n * fact step (n-step)

let Factorial (param:obj list) =
    let x = (extractFloat(param.[0]))
    if x=nan || x<0. then nan else float (fact 1L (int64 x))

(*let rec private fib =
    let dict = new System.Collections.Generic.Dictionary<_,_>()
    fun n ->
        match dict.TryGetValue(n) with
        | true, v -> v
        | false, _ -> 
            let temp =
                if n=0. || n=1. then n
                else fib(n-1.) + fib(n-2.)
            dict.Add(n, temp)
            temp
*)


let Factorial2 (param:obj list) =
    let x = (extractFloat(param.[0]))
    if x=nan || x<0. then nan else float (fact 2L (int64 x))


let Sum (param:obj list) =
    let mutable tot = 0.
    for el in param do
        tot <- match el with
               | :? float as num -> tot + num
               | :? bool  as b   -> if b then tot + 1. else tot
               | :? string as s  -> 
                    if String.IsNullOrWhiteSpace(s) then tot
                    else
                        let mutable v = 0.
                        if Double.TryParse(s, &v) then tot+v else nan     
               | null -> tot
               | :? IRange as r   ->
                    let mutable rtot = 0.
                    for el in r.ValueSeq() do
                        match el with
                        | :? float as f -> rtot<-rtot+f
                        | _ -> ()
                    tot + rtot
               | _ -> nan
    tot

let Product (param:obj list) =
    let mutable tot = 1.
    for el in param do
        tot <- match el with
               | :? float as num -> tot * num
               | :? bool  as b   -> if b then tot else 0.
               | :? string as s  -> 
                    let mutable v = 0.
                    if Double.TryParse(s, &v) then tot*v else nan
               | null -> tot
               | :? IRange as r   ->
                    let mutable rtot = 1.
                    for el in r.ValueSeq() do
                        match el with
                        | :? float as f -> rtot<-rtot*f
                        | _ -> ()
                    tot * rtot
               | _ -> nan
    tot

let Average (param:obj list) =
    let mutable tot, n = 0.,0
    for el in param do
        match el with
            | :? float as f   -> tot<-tot+f; n<-n+1
            | :? bool as b    -> n<-n+1; if b then tot<-tot+1.
            | :? string as s  -> 
                n<-n+1; 
                let mutable v = 0.
                if Double.TryParse(s, &v) then tot<-tot+v else tot<-nan
            | null -> n<-n+1
            | :? IRange as r   ->
                let mutable rtot = 1.
                for el in r.ValueSeq() do
                    match el with
                    | :? float as f -> tot<-tot+f; n<-n+1
                    | _ -> ()
            | _ -> tot<-nan
    tot/(float n)

let Div (param:obj list) = 
    let num = extractFloat(param.[0])
    let den = extractFloat(param.[1])
    floor (num/den)

let Mod (param:obj list) = 
    let num = extractFloat(param.[0])
    let den = extractFloat(param.[1])
    num % den


let If (param:obj list) = 
    let cond = match param.[0] with
                | :? bool as b -> b
                | :? float as f -> if f=0. then false else true
                | _ -> false

    if cond then
        if param.Length>1 && param.[1]<>null 
        then param.[1] else (box 0.)
    else
        if param.Length>2 && param.[2]<>null
        then param.[2] else (box 0.)


let Count (param:obj list) =
    let mutable tot = 0
    for el in param do
        match el with
        | :? float -> tot<-tot + 1
        | :? bool as b  -> if b then tot<-tot+1
        | :? string as s  -> 
                let mutable v = 0.
                if Double.TryParse(s, &v) then tot<-tot+1
        | :? IRange as r   ->
            for el in r.ValueSeq() do
                match el with
                | :? float as f -> tot<-tot+1
                | _ -> ()
        | _ -> ()
    float tot


(* Nelle seguenti funzioni deve essere modificata 
   la gestione degli oggetti di tipo IRange.. 


let Harmonic (param:obj list) =
    let mutable tot, n = 0.,0
    for el in param do
        match el with
            | :? float as f -> tot<-tot+(1./f); n<-n+1
            | :? Range as rng -> tot<-tot + rng.SumFloatInverse; n<-n+rng.CountNotNull
            | :? bool as b    -> n<-n+1; if b then tot<-tot+1.
            | :? string as s  -> 
                    n<-n+1
                    let mutable v = 0.
                    if Double.TryParse(s, &v) then tot<-tot+(1./v) else tot<-nan
            | null -> n<-n+1
            | _ -> tot<-nan
    (float n)/tot


let Max (param:obj list) =
    let mutable mas = -infinity
    for el in param do
        let temp = match el with
                    | :? float as n -> n
                    | :? Range as r -> r.MaxFloat
                    | :? bool as b    -> if b then 1. else 0.
                    | :? string as s  -> 
                        let mutable v = 0.
                        if Double.TryParse(s, &v) then v else nan
                    | null -> 0.
                    | _ -> nan
        if temp>m then m<-temp
    if m=(-infinity) then 0.
    else m

let MaxA (param:obj list) =
    let mutable m = -infinity
    for el in param do
        let temp = match el with
                    | :? float as n -> n
                    | :? Range as r -> r.MaxA
                    | :? bool as b    -> if b then 1. else 0.
                    | :? string as s  -> 
                        let mutable v = 0.
                        if Double.TryParse(s, &v) then v else nan
                    | null -> 0.
                    | _ -> nan
        if temp>m then m<-temp
    if m=(-infinity) then 0.
    else m

let Min (param:obj list) =
    let mutable m = infinity
    for el in param do
        let temp = match el with
                    | :? float as n -> n
                    | :? Range as r -> r.MinFloat
                    | :? bool as b    -> if b then 1. else 0.
                    | :? string as s  -> 
                        let mutable v = 0.
                        if Double.TryParse(s, &v) then v else nan
                    | null -> 0.
                    | _ -> nan
        if temp<m then m<-temp
    if m=infinity then 0.
    else m

let MinA (param:obj list) =
    let mutable m = infinity
    for el in param do
        let temp = match el with
                    | :? float as n -> n
                    | :? Range as r -> r.MinA
                    | :? bool as b    -> if b then 1. else 0.
                    | :? string as s  -> 
                        let mutable v = 0.
                        if Double.TryParse(s, &v) then v else nan
                    | null -> 0.
                    | _ -> nan
        if temp<m then m<-temp
    if m=infinity then 0.
    else m

*)